@@ -5,8 +5,9 @@ from django.conf.urls import url |
||
5 | 5 |
from account import views as account_views |
6 | 6 |
from group import views as group_views |
7 | 7 |
from message import views as message_views |
8 |
-from photo import views as photo_views |
|
9 | 8 |
from operation import views as op_views |
9 |
+from pay import views as pay_views |
|
10 |
+from photo import views as photo_views |
|
10 | 11 |
|
11 | 12 |
|
12 | 13 |
# 帐户相关 |
@@ -69,3 +70,9 @@ urlpatterns += [ |
||
69 | 70 |
url(r'^op/upgrade$', op_views.upgrade_api, name='upgrade_api'), # APP 升级 |
70 | 71 |
url(r'^op/splash$', op_views.splash_api, name='splash_api'), # 启动页面 |
71 | 72 |
] |
73 |
+ |
|
74 |
+# 支付相关 |
|
75 |
+urlpatterns += [ |
|
76 |
+ url(r'^order/create$', pay_views.order_create_api, name='order_create_api'), # 订单创建 |
|
77 |
+ url(r'^order/notify_url$', pay_views.notify_url_api, name='notify_url_api'), # 支付异步通知回调地址 |
|
78 |
+] |
@@ -25,6 +25,9 @@ import os |
||
25 | 25 |
import shortuuid |
26 | 26 |
|
27 | 27 |
|
28 |
+r = settings.REDIS_CACHE |
|
29 |
+ |
|
30 |
+ |
|
28 | 31 |
@transaction.atomic |
29 | 32 |
def group_create_api(request): |
30 | 33 |
user_id = request.POST.get('user_id', '') |
@@ -0,0 +1,11 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+import redis |
|
4 |
+ |
|
5 |
+ |
|
6 |
+def redis_connect(settings): |
|
7 |
+ return redis.StrictRedis(**{ |
|
8 |
+ 'host': settings.get('HOST', ''), |
|
9 |
+ 'port': settings.get('PORT', 0), |
|
10 |
+ 'password': '{user}:{pwd}'.format(settings.get('USER', ''), settings.get('PASSWORD', '')) if settings.get('USER', '') else '' |
|
11 |
+ }) |
@@ -46,6 +46,7 @@ INSTALLED_APPS = ( |
||
46 | 46 |
'group', |
47 | 47 |
'message', |
48 | 48 |
'operation', |
49 |
+ 'pay', |
|
49 | 50 |
'photo', |
50 | 51 |
) |
51 | 52 |
|
@@ -158,6 +159,41 @@ REST_FRAMEWORK = { |
||
158 | 159 |
'PAGE_SIZE': 1 |
159 | 160 |
} |
160 | 161 |
|
162 |
+# Redis 设置 |
|
163 |
+REDIS = { |
|
164 |
+ 'default': { |
|
165 |
+ 'HOST': '127.0.0.1', |
|
166 |
+ 'PORT': 6379, |
|
167 |
+ 'USER': '', |
|
168 |
+ 'PASSWORD': '' |
|
169 |
+ } |
|
170 |
+} |
|
171 |
+ |
|
172 |
+# Redis 缓存时间设置 |
|
173 |
+REDIS_EXPIRED_HOUR = 3600 # 60 * 60 |
|
174 |
+REDIS_EXPIRED_DAY = 86400 # 24 * 60 * 60 |
|
175 |
+REDIS_EXPIRED_WEEK = 604800 # 7 * 24 * 60 * 60 |
|
176 |
+REDIS_EXPIRED_MONTH = 2678400 # 31 * 24 * 60 * 60 |
|
177 |
+REDIS_EXPIRED_YEAR = 31622400 # 366 * 24 * 60 * 60 |
|
178 |
+ |
|
179 |
+# 微信设置 |
|
180 |
+WECHAT = { |
|
181 |
+ 'token': '5201314', |
|
182 |
+ 'appID': '', |
|
183 |
+ 'appsecret': '', |
|
184 |
+ 'mchID': '', |
|
185 |
+ 'apiKey': '', |
|
186 |
+} |
|
187 |
+ |
|
188 |
+WECHAT_GET_CODE = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect' |
|
189 |
+WECHAT_GET_CODE_USERINFO = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect' |
|
190 |
+WECHAT_GET_ACCESS_TOKEN = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code' |
|
191 |
+ |
|
192 |
+WECHAT_GET_USERINFO = 'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s' |
|
193 |
+ |
|
194 |
+WXPAY_NOTIFY_SUCCESS = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>' |
|
195 |
+WXPAY_NOTIFY_FAIL = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[XML PARSE FAIL]]></return_msg></xml>' |
|
196 |
+ |
|
161 | 197 |
# 唯一标识设置 |
162 | 198 |
CURTAIL_UUID_LENGTH = 7 |
163 | 199 |
|
@@ -168,10 +204,9 @@ WATERMARK_LOGO = os.path.join(PROJ_DIR, 'static/pai2/img/paiai_96_96.png').repla |
||
168 | 204 |
THUMBNAIL_MAX_WIDTH = 360 |
169 | 205 |
|
170 | 206 |
# 域名设置 |
171 |
-# DOMAIN = 'http://xfoto.com.cn' |
|
172 |
-# IMG_DOMAIN = 'http://img.xfoto.com.cn' |
|
173 | 207 |
DOMAIN = 'http://pai.ai' |
174 | 208 |
IMG_DOMAIN = 'http://img.pai.ai' |
209 |
+API_DOMAIN = 'http://api.pai.ai' |
|
175 | 210 |
|
176 | 211 |
# 消息图片设置 |
177 | 212 |
PAI2_LOGO_URL = DOMAIN + '/static/pai2/img/paiai_96_96.png' |
@@ -186,3 +221,9 @@ try: |
||
186 | 221 |
from local_settings import * |
187 | 222 |
except ImportError: |
188 | 223 |
pass |
224 |
+ |
|
225 |
+try: |
|
226 |
+ from func_settings import redis_connect |
|
227 |
+ REDIS_CACHE = redis_connect(REDIS.get('default', {})) |
|
228 |
+except ImportError: |
|
229 |
+ REDIS_CACHE = None |
@@ -0,0 +1,13 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.contrib import admin |
|
4 |
+ |
|
5 |
+from pay.models import OrderInfo |
|
6 |
+ |
|
7 |
+ |
|
8 |
+class OrderInfoAdmin(admin.ModelAdmin): |
|
9 |
+ list_display = ('order_id', 'from_uid', 'to_lid', 'to_uid', 'pay_status', 'paid_at', 'status', 'created_at', 'updated_at') |
|
10 |
+ list_filter = ('pay_status', 'status') |
|
11 |
+ |
|
12 |
+ |
|
13 |
+admin.site.register(OrderInfo, OrderInfoAdmin) |
@@ -0,0 +1,35 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+from __future__ import unicode_literals |
|
3 |
+ |
|
4 |
+from django.db import models, migrations |
|
5 |
+import shortuuidfield.fields |
|
6 |
+ |
|
7 |
+ |
|
8 |
+class Migration(migrations.Migration): |
|
9 |
+ |
|
10 |
+ dependencies = [ |
|
11 |
+ ] |
|
12 |
+ |
|
13 |
+ operations = [ |
|
14 |
+ migrations.CreateModel( |
|
15 |
+ name='OrderInfo', |
|
16 |
+ fields=[ |
|
17 |
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
18 |
+ ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', db_index=True, verbose_name='status')), |
|
19 |
+ ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), |
|
20 |
+ ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), |
|
21 |
+ ('order_id', shortuuidfield.fields.ShortUUIDField(help_text='\u8ba2\u5355\u552f\u4e00\u6807\u8bc6', max_length=22, editable=False, db_index=True, blank=True)), |
|
22 |
+ ('from_uid', models.CharField(help_text='\u4ed8\u6b3e\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, verbose_name='from_uid', db_index=True)), |
|
23 |
+ ('to_lid', models.CharField(max_length=255, blank=True, help_text='\u6536\u6b3e\u6444\u5f71\u5e08\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='to_lid', db_index=True)), |
|
24 |
+ ('to_uid', models.CharField(max_length=255, blank=True, help_text='\u6536\u6b3e\u7528\u6237\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='to_uid', db_index=True)), |
|
25 |
+ ('body', models.CharField(help_text='\u5546\u54c1\u63cf\u8ff0', max_length=255, null=True, verbose_name='body', blank=True)), |
|
26 |
+ ('total_fee', models.IntegerField(default=0, help_text='\u603b\u91d1\u989d', verbose_name='total_fee')), |
|
27 |
+ ('pay_status', models.IntegerField(default=0, help_text='\u652f\u4ed8\u72b6\u6001', db_index=True, verbose_name='pay_status', choices=[(0, '\u5f85\u652f\u4ed8'), (1, '\u5df2\u652f\u4ed8')])), |
|
28 |
+ ('paid_at', models.DateTimeField(help_text='\u652f\u4ed8\u65f6\u95f4', null=True, verbose_name='paid_at', blank=True)), |
|
29 |
+ ], |
|
30 |
+ options={ |
|
31 |
+ 'verbose_name': 'orderinfo', |
|
32 |
+ 'verbose_name_plural': 'orderinfo', |
|
33 |
+ }, |
|
34 |
+ ), |
|
35 |
+ ] |
@@ -0,0 +1,52 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.conf import settings |
|
4 |
+from django.db import models |
|
5 |
+from django.utils.translation import ugettext_lazy as _ |
|
6 |
+ |
|
7 |
+from shortuuidfield import ShortUUIDField |
|
8 |
+ |
|
9 |
+from pai2.basemodels import CreateUpdateMixin |
|
10 |
+ |
|
11 |
+ |
|
12 |
+class OrderInfo(CreateUpdateMixin): |
|
13 |
+ WAITING_PAY = 0 |
|
14 |
+ PAID = 1 |
|
15 |
+ # DELETED = 2 |
|
16 |
+ |
|
17 |
+ PAY_STATUS = ( |
|
18 |
+ (WAITING_PAY, u'待支付'), |
|
19 |
+ (PAID, u'已支付'), |
|
20 |
+ # (DELETED, u'已删除'), |
|
21 |
+ ) |
|
22 |
+ |
|
23 |
+ order_id = ShortUUIDField(_(u'order_id'), max_length=255, help_text=u'订单唯一标识', db_index=True) |
|
24 |
+ |
|
25 |
+ from_uid = models.CharField(_(u'from_uid'), max_length=255, help_text=u'付款用户唯一标识', db_index=True) |
|
26 |
+ to_lid = models.CharField(_(u'to_lid'), max_length=255, blank=True, null=True, help_text=u'收款摄影师唯一标识', db_index=True) |
|
27 |
+ to_uid = models.CharField(_(u'to_uid'), max_length=255, blank=True, null=True, help_text=u'收款用户唯一标识', db_index=True) |
|
28 |
+ |
|
29 |
+ body = models.CharField(_(u'body'), max_length=255, blank=True, null=True, help_text=u'商品描述') |
|
30 |
+ total_fee = models.IntegerField(_(u'total_fee'), default=0, help_text=u'总金额') |
|
31 |
+ |
|
32 |
+ pay_status = models.IntegerField(_(u'pay_status'), choices=PAY_STATUS, default=WAITING_PAY, help_text=u'支付状态', db_index=True) |
|
33 |
+ paid_at = models.DateTimeField(_(u'paid_at'), blank=True, null=True, help_text=_(u'支付时间')) |
|
34 |
+ |
|
35 |
+ class Meta: |
|
36 |
+ verbose_name = _('orderinfo') |
|
37 |
+ verbose_name_plural = _('orderinfo') |
|
38 |
+ |
|
39 |
+ def __unicode__(self): |
|
40 |
+ return u'{0.pk}'.format(self) |
|
41 |
+ |
|
42 |
+ @property |
|
43 |
+ def data(self): |
|
44 |
+ return { |
|
45 |
+ 'order_id': self.order_id, |
|
46 |
+ 'from_uid': self.from_uid, |
|
47 |
+ 'to_lid': self.to_lid, |
|
48 |
+ 'to_uid': self.to_uid, |
|
49 |
+ 'pay_status': self.pay_status, |
|
50 |
+ 'paid_at': self.paid_at, |
|
51 |
+ 'created_at': self.created_at, |
|
52 |
+ } |
@@ -0,0 +1,3 @@ |
||
1 |
+from django.test import TestCase |
|
2 |
+ |
|
3 |
+# Create your tests here. |
@@ -0,0 +1,94 @@ |
||
1 |
+# -*- coding: utf-8 -*- |
|
2 |
+ |
|
3 |
+from django.conf import settings |
|
4 |
+from django.db import transaction |
|
5 |
+from django.http import JsonResponse |
|
6 |
+from django.shortcuts import HttpResponse |
|
7 |
+ |
|
8 |
+from pay.models import OrderInfo |
|
9 |
+ |
|
10 |
+from utils.errno_utils import OrderStatusCode |
|
11 |
+from utils.response_utils import response |
|
12 |
+ |
|
13 |
+from TimeConvert import TimeConvert as tc |
|
14 |
+from wechatpy import WeChatPay, WeChatPayException |
|
15 |
+ |
|
16 |
+import xmltodict |
|
17 |
+ |
|
18 |
+WECHAT = settings.WECHAT |
|
19 |
+ |
|
20 |
+wxpay = WeChatPay(WECHAT['appID'], WECHAT['apiKey'], WECHAT['mchID']) |
|
21 |
+ |
|
22 |
+ |
|
23 |
+@transaction.atomic |
|
24 |
+def order_create_api(request): |
|
25 |
+ from_uid = request.POST.get('from_uid', '') |
|
26 |
+ to_lid = request.POST.get('to_lid', '') |
|
27 |
+ to_uid = request.POST.get('to_uid', '') |
|
28 |
+ |
|
29 |
+ body = request.POST.get('body', '') # 商品描述 |
|
30 |
+ total_fee = int(request.POST.get('total_fee', 0)) # 总金额,单位分 |
|
31 |
+ |
|
32 |
+ # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里 |
|
33 |
+ trade_type = request.POST.get('trade_type', '') |
|
34 |
+ |
|
35 |
+ # 生成订单 |
|
36 |
+ order = OrderInfo.objects.create(from_uid=from_uid, to_lid=to_lid, to_uid=to_uid, total_fee=total_fee) |
|
37 |
+ |
|
38 |
+ try: |
|
39 |
+ prepay_data = wxpay.order.create( |
|
40 |
+ body=body, |
|
41 |
+ notify_url=settings.API_DOMAIN + '/order/notify_url', |
|
42 |
+ out_trade_no=order.order_id, |
|
43 |
+ total_fee=total_fee, |
|
44 |
+ trade_type=trade_type, |
|
45 |
+ # user_id=None, # 可选,用户在商户appid下的唯一标识。trade_type=JSAPI,此参数必传 |
|
46 |
+ ) |
|
47 |
+ except WeChatPayException: |
|
48 |
+ return response(OrderStatusCode.WX_UNIFIED_ORDER_FAIL) |
|
49 |
+ |
|
50 |
+ prepay_id = prepay_data.get('prepay_id', '') |
|
51 |
+ wxpay_params = wxpay.jsapi.get_jsapi_params(prepay_id) |
|
52 |
+ |
|
53 |
+ return JsonResponse({ |
|
54 |
+ 'status': 200, |
|
55 |
+ 'data': { |
|
56 |
+ 'order_id': order.order_id, |
|
57 |
+ 'prepay_id': prepay_id, |
|
58 |
+ 'wxpay_params': wxpay_params, |
|
59 |
+ } |
|
60 |
+ }) |
|
61 |
+ |
|
62 |
+ |
|
63 |
+def order_paid_success(order): |
|
64 |
+ if order.pay_status == OrderInfo.PAID: |
|
65 |
+ return |
|
66 |
+ |
|
67 |
+ order.pay_status = OrderInfo.PAID |
|
68 |
+ order.paid_at = tc.utc_datetime() |
|
69 |
+ order.save() |
|
70 |
+ |
|
71 |
+ |
|
72 |
+@transaction.atomic |
|
73 |
+def notify_url_api(request): |
|
74 |
+ try: |
|
75 |
+ data = xmltodict.parse(request.body)['xml'] |
|
76 |
+ except xmltodict.ParsingInterrupted: |
|
77 |
+ # 解析 XML 失败 |
|
78 |
+ return HttpResponse(settings.WXPAY_NOTIFY_FAIL) |
|
79 |
+ |
|
80 |
+ out_trade_no = data.get('out_trade_no', '') |
|
81 |
+ return_code = data.get('return_code', '') |
|
82 |
+ result_code = data.get('result_code', '') |
|
83 |
+ |
|
84 |
+ if return_code != 'SUCCESS' or result_code != 'SUCCESS': |
|
85 |
+ return HttpResponse(settings.WXPAY_NOTIFY_FAIL) |
|
86 |
+ |
|
87 |
+ try: |
|
88 |
+ order = OrderInfo.objects.get(order=out_trade_no) |
|
89 |
+ except OrderInfo.DoesNotExist: |
|
90 |
+ return HttpResponse(settings.WXPAY_NOTIFY_FAIL) |
|
91 |
+ |
|
92 |
+ order_paid_success(order) |
|
93 |
+ |
|
94 |
+ return HttpResponse(settings.WXPAY_NOTIFY_SUCCESS) |
@@ -63,7 +63,8 @@ class PhotosInfo(CreateUpdateMixin): |
||
63 | 63 |
def r_photo_url(self): |
64 | 64 |
return u'{0}/{1}'.format(settings.IMG_DOMAIN, self.r_photo_path) if self.r_photo_path else '' |
65 | 65 |
|
66 |
- def _data(self): |
|
66 |
+ @property |
|
67 |
+ def data(self): |
|
67 | 68 |
return { |
68 | 69 |
'pk': self.pk, |
69 | 70 |
'user': self.lensman_id, |
@@ -71,7 +72,8 @@ class PhotosInfo(CreateUpdateMixin): |
||
71 | 72 |
'photo': self.photo_id, |
72 | 73 |
} |
73 | 74 |
|
74 |
- def _detail(self): |
|
75 |
+ @property |
|
76 |
+ def detail(self): |
|
75 | 77 |
return { |
76 | 78 |
'pk': self.pk, |
77 | 79 |
'user': self.lensman_id, |
@@ -79,6 +81,3 @@ class PhotosInfo(CreateUpdateMixin): |
||
79 | 81 |
'photo': self.photo_id, |
80 | 82 |
'photo_url': self.p_photo_url, |
81 | 83 |
} |
82 |
- |
|
83 |
- data = property(_data) |
|
84 |
- detail = property(_detail) |
@@ -2,6 +2,7 @@ CodeConvert==2.0.4 |
||
2 | 2 |
Django==1.8.4 |
3 | 3 |
MySQL-python==1.2.5 |
4 | 4 |
TimeConvert==1.1.6 |
5 |
+cryptography==1.2.1 |
|
5 | 6 |
django-curtail-uuid==1.0.0 |
6 | 7 |
django-multidomain==1.1.4 |
7 | 8 |
django-shortuuidfield==0.1.3 |
@@ -12,5 +13,7 @@ kkconst==1.1.2 |
||
12 | 13 |
pep8==1.6.2 |
13 | 14 |
pillow==2.9.0 |
14 | 15 |
pytz==2015.7 |
16 |
+redis==2.10.5 |
|
15 | 17 |
shortuuid==0.4.2 |
16 | 18 |
uWSGI==2.0.11.1 |
19 |
+wechatpy==1.2.5 |
@@ -15,6 +15,7 @@ class StatusCodeField(ConstIntField): |
||
15 | 15 |
|
16 | 16 |
|
17 | 17 |
class UserStatusCode(BaseStatusCode): |
18 |
+ """ 摄影师/用户相关错误码 400x & 401x """ |
|
18 | 19 |
LENSMAN_NOT_FOUND = StatusCodeField(4000, u'Lensman Not Found', description=u'摄影师不存在') |
19 | 20 |
LENSMAN_PASSWORD_ERROR = StatusCodeField(4001, u'Lensman Password Error', description=u'摄影师密码错误') |
20 | 21 |
USERNAME_HAS_REGISTERED = StatusCodeField(4010, u'Username Has Registered', description=u'用户名已注册') |
@@ -23,27 +24,36 @@ class UserStatusCode(BaseStatusCode): |
||
23 | 24 |
|
24 | 25 |
|
25 | 26 |
class PhotoStatusCode(BaseStatusCode): |
27 |
+ """ 照片相关错误码 403x """ |
|
26 | 28 |
PARAMS_ERROR = StatusCodeField(4039, u'Params Error', description=u'参数错误') |
27 | 29 |
|
28 | 30 |
|
29 | 31 |
class GroupStatusCode(BaseStatusCode): |
32 |
+ """ 群组相关错误码 402x """ |
|
30 | 33 |
GROUP_NOT_FOUND = StatusCodeField(4020, u'Group Not Found', description=u'群组不存在') |
31 | 34 |
GROUP_HAS_LOCKED = StatusCodeField(4021, u'Group Has Locked', description=u'群组已锁定') |
32 | 35 |
NOT_GROUP_ADMIN = StatusCodeField(4022, u'Not Group Admin', description=u'非群组管理员') |
33 | 36 |
NO_UPDATE_PERMISSION = StatusCodeField(40220, u'No Update Permission', description=u'没有更新权限') |
34 | 37 |
NO_LOCK_PERMISSION = StatusCodeField(40221, u'No Lock Permission', description=u'没有锁定权限') |
35 |
- NO_UNLOCK_PERMISSION = StatusCodeField(40221, u'No Unlock Permission', description=u'没有解锁权限') |
|
36 |
- NO_REMOVE_PERMISSION = StatusCodeField(40222, u'No Remove Permission', description=u'没有移除权限') |
|
37 |
- NO_PASS_PERMISSION = StatusCodeField(40223, u'No Pass Permission', description=u'没有通过权限') |
|
38 |
- NO_REFUSE_PERMISSION = StatusCodeField(40224, u'No Refuse Permission', description=u'没有拒绝权限') |
|
38 |
+ NO_UNLOCK_PERMISSION = StatusCodeField(40222, u'No Unlock Permission', description=u'没有解锁权限') |
|
39 |
+ NO_REMOVE_PERMISSION = StatusCodeField(40223, u'No Remove Permission', description=u'没有移除权限') |
|
40 |
+ NO_PASS_PERMISSION = StatusCodeField(40224, u'No Pass Permission', description=u'没有通过权限') |
|
41 |
+ NO_REFUSE_PERMISSION = StatusCodeField(40225, u'No Refuse Permission', description=u'没有拒绝权限') |
|
39 | 42 |
DUPLICATE_JOIN_REQUEST = StatusCodeField(4027, u'Duplicate Join Request', description=u'重复加群申请') |
40 | 43 |
JOIN_REQUEST_NOT_FOUND = StatusCodeField(4028, u'Join Request Not Found', description=u'加群申请不存在') |
41 | 44 |
GROUP_USER_NOT_FOUND = StatusCodeField(4029, u'Group User Not Found', description=u'该用户不在群组') |
42 | 45 |
|
43 | 46 |
|
44 | 47 |
class GroupPhotoStatusCode(BaseStatusCode): |
48 |
+ """ 飞图相关错误码 403x """ |
|
45 | 49 |
GROUP_PHOTO_NOT_FOUND = StatusCodeField(4030, u'Group Photo Not Found', description=u'飞图不存在') |
46 | 50 |
|
47 | 51 |
|
52 |
+class OrderStatusCode(BaseStatusCode): |
|
53 |
+ """ 订单/支付相关错误码 404x """ |
|
54 |
+ WX_UNIFIED_ORDER_FAIL = StatusCodeField(4040, u'WX Unified Order Fail', description=u'微信统一下单失败') |
|
55 |
+ |
|
56 |
+ |
|
48 | 57 |
class MessageStatusCode(BaseStatusCode): |
58 |
+ """ 消息相关错误码 409x """ |
|
49 | 59 |
MESSAGE_NOT_FOUND = StatusCodeField(4091, u'Message Not Found', description=u'消息不存在') |